// 071114;080505 alexch
//
// ==========  This file is under  LGPL, the GNU Lesser General Public Licence
// ==========  Dialing Lemmatizer (www.aot.ru)
// ==========  Copyright by Alexey Sokirko 
//
// ==========  Visual Studio project for 32/64 bit DLL-wrapper
// ==========  Created by Hulbee AG

#pragma warning(disable: 4786)
#include "StlFileF.h"
#include "StlWinF.h"
#include <malloc.h>
#include <share.h>
#include <assert.h>

namespace StlFunc {

// {
template <class STR_TYPE> static inline STR_TYPE& TIncludePathDelimiter(STR_TYPE &pathName)
{
	size_t len = pathName.size();
	if (len == 0 || pathName[len - 1] != PathDelimiter)
		pathName += PathDelimiter;
	return pathName;
}

str_t& IncludePathDelimiter(str_t &pathName)
{
	return TIncludePathDelimiter<str_t>(pathName);
}

wstr_t& IncludePathDelimiter(wstr_t &pathName)
{
	return TIncludePathDelimiter<wstr_t>(pathName);
}
// }

// {
template <class STR_TYPE> static inline STR_TYPE& TExcludePathDelimiter(STR_TYPE &pathName)
{
	size_t len = pathName.size();
	if (len > 0 && pathName[len - 1] == PathDelimiter)
		pathName.erase(len - 1, 1);
	return pathName;
}

str_t& ExcludePathDelimiter(str_t &pathName)
{
	return TExcludePathDelimiter<str_t>(pathName);
}

wstr_t& ExcludePathDelimiter(wstr_t &pathName)
{
	return TExcludePathDelimiter<wstr_t>(pathName);
}
// }

static int LastDelimiter(const wchar_t *delimiters, const wstr_t &s)
{
	wstr_t::const_iterator s_start = s.begin(), it = s.end();
	while (it > s_start) {
		--it;
		if (wcschr(delimiters, *it) != 0)
			return int(it - s_start);
	}
	return -1;
}

static int LastDelimiter(const char *delimiters, const str_t &s)
{
	str_t::const_iterator s_start = s.begin(), it = s.end();
	while (it > s_start) {
		--it;
		if (strchr(delimiters, *it) != 0)
			return int(it - s_start);
	}
	return -1;
}

wstr_t& ExtractFileDrive(const wstr_t &src, wstr_t &dst)
{
	size_t sz = src.size();
	if (sz >= 2) {
		if (src[1] == DriveDelimiterW) {
			dst.assign(src, 0, 2);
			return dst += PathDelimiterW;
		}
		if (src[0] == PathDelimiterW && src[1] == PathDelimiterW) {
			size_t i, j;
			for (i = 2, j = 0; i < sz; ++i)
				if (src[i] == PathDelimiterW)
					if (++j == 2)
						break;
			dst.assign(src, 0, i);
			return IncludePathDelimiter(dst);
		}
	}
	return dst.erase();
}

wstr_t& ExtractFileDrive(wstr_t &pathName)
{
	wstr_t w = pathName;
	return ExtractFileDrive(w, pathName);
}

static wchar_t FileNameDelims[3] = {L':', PathDelimiterW, 0};

void ExtractFilePath(const wstr_t &src, wstr_t &dst)
{
	int i = LastDelimiter(FileNameDelims, src);
	if (i >= 0)
		dst.assign(src, 0, i + 1);
	else
		dst.erase();
}

wstr_t& ExtractFilePath(wstr_t &fileName)
{
	int i = LastDelimiter(FileNameDelims, fileName);
	if (i >= 0)
		fileName.erase(i + 1);
	return fileName;
}

void ExtractFileName(const wstr_t &src, wstr_t &dst)
{
	int i = LastDelimiter(FileNameDelims, src);
	if (i >= 0)
		dst.assign(src, i + 1, wstr_t::npos);
	else
		dst = src;
}

wstr_t& ExtractFileName(wstr_t &fileName)
{
	int i = LastDelimiter(FileNameDelims, fileName);
	if (i >= 0)
		fileName.erase(0, i + 1);
	return fileName;
}

void ParseFileName(const wchar_t *fullName, wstr_t &fileName, wstr_t &filePath)
{
	fileName = fullName;
	ParseFileName(fileName, filePath);
}

void ParseFileName(wstr_t &fileName, wstr_t &filePath)
{
	int i = LastDelimiter(FileNameDelims, fileName);
	if (i >= 0) {
		++i;
		filePath.assign(fileName, 0, i);
		fileName.erase(0, i);
	}
	else
		filePath.erase();
}

static wchar_t ExtDelimsW[4] = {L'.', L':', PathDelimiterW, 0};
static char ExtDelims[4] = {'.', ':', PathDelimiter, 0};

// {
#define EXTRACT_FILE_EXT1(STR_TYPE, DELIMS) void ExtractFileExt(const STR_TYPE &src, STR_TYPE &dst) \
{ \
	int i = LastDelimiter(DELIMS, src); \
	if (i >= 0 && src[i] == '.') \
		dst.assign(src, i + 1, STR_TYPE::npos); \
	else \
		dst.erase(); \
}

EXTRACT_FILE_EXT1(str_t, ExtDelims)
EXTRACT_FILE_EXT1(wstr_t, ExtDelimsW)
// }

// {
#define EXTRACT_FILE_EXT2(STR_TYPE, DELIMS) STR_TYPE& ExtractFileExt(STR_TYPE &fileName) \
{ \
	int i = LastDelimiter(DELIMS, fileName); \
	if (i >= 0 && fileName[i] == '.') \
		fileName.erase(0, i + 1); \
	else \
		fileName.erase(); \
	return fileName; \
}

EXTRACT_FILE_EXT2(str_t, ExtDelims)
EXTRACT_FILE_EXT2(wstr_t, ExtDelimsW)
// }

// {
#define GET_FILE_EXT(STR_TYPE, DELIMS) STR_TYPE GetFileExt(const STR_TYPE &src) \
{ \
	int i = LastDelimiter(DELIMS, src); \
	if (i >= 0 && src[i] == '.') \
		return src.substr(i + 1); \
	return STR_TYPE(); \
}

GET_FILE_EXT(str_t, ExtDelims)
GET_FILE_EXT(wstr_t, ExtDelimsW)
// }

// {
#define EXCLUDE_FILE_EXT(STR_TYPE, DELIMS) STR_TYPE& ExcludeFileExt(STR_TYPE &fileName) \
{ \
	int i = LastDelimiter(DELIMS, fileName); \
	if (i >= 0 && fileName[i] == '.') \
		fileName.erase(i); \
	return fileName; \
}

EXCLUDE_FILE_EXT(str_t, ExtDelims)
EXCLUDE_FILE_EXT(wstr_t, ExtDelimsW)
// }

// {
#define CHANGE_FILE_EXT(STR_TYPE, DELIMS) STR_TYPE& ChangeFileExt( \
	STR_TYPE &fileName, const STR_TYPE &newExt) \
{ \
	if (newExt.empty() || newExt.size() == 1 && newExt[0] == '.') { \
		ExcludeFileExt(fileName); \
		return fileName; \
	} \
	int i = LastDelimiter(DELIMS, fileName); \
	bool dot = newExt[0] == '.'; \
	if (i >= 0 && fileName[i] == '.') { \
		if (!dot) \
			++i; \
		fileName.erase(i); \
		fileName += newExt; \
	} \
	else { \
		if (!dot) \
			fileName += '.'; \
		fileName += newExt; \
	} \
	return fileName; \
}

CHANGE_FILE_EXT(str_t, ExtDelims)
CHANGE_FILE_EXT(wstr_t, ExtDelimsW)
// }

#ifndef ONLY_NATIVE

bool EqualFileNames(const wstr_t &name1, const wstr_t &name2)
{
	int i1 = LastDelimiter(FileNameDelims, name1) + 1; // (-1 + 1 == 0) if no delimiters found
	size_t n1 = name1.size() - i1;
	assert(n1 <= INT_MAX);
	int i2 = LastDelimiter(FileNameDelims, name2) + 1;
	size_t n2 = name2.size() - i2;
	assert(n2 <= INT_MAX);
	if (n1 != n2)
		return false;
	return CompareStringW(0, NORM_IGNORECASE | SORT_STRINGSORT, name1.data() + i1, (int)n1,
		name2.data() + i2, (int)n2) == CSTR_EQUAL;
}

bool ExpandPathName(wstr_t &pathName)
{
	wchar_t buf[MaxFileNameLen], *filePart;
	DWORD n = GetFullPathNameW(pathName.c_str(), ARRAY_HIGH(buf), buf, &filePart);
	if (n > 0 && n < ARRAY_HIGH(buf)) {
		buf[ARRAY_HIGH(buf)] = 0;
		pathName = buf;
		return true;
	}
	else
		return false;
}

/*static void ProcessExpanded(wstr_t &expName)
{
	DWORD n = expName.size();
	if (!n)
		return;
	--n;
	bool was_bs;
	if (expName[n] == PathDelimiterW) {
		if (n > 0 && expName[n - 1] == L':')
			return;
		expName.erase(n);
		--n;
		was_bs = true;
	}
	else {
		if (expName[n] == L':')
			return;
		was_bs = false;
	}
	wstr_t path, name = expName;
	ParseFileName(path, name);
	if (name.find(L'*') == wstr_t::npos && name.find(L'?') == wstr_t::npos) {
		WIN32_FIND_DATAW findData;
		HANDLE hFind = FindFirstFileW(expName.c_str(), &findData);
		if (hFind == INVALID_HANDLE_VALUE)
			return;
		FindClose(hFind);
		if (findData.cFileName[0] == L'.' && findData.cFileName[1] == 0)
			return;
		findData.cFileName[ARRAY_HIGH(findData.cFileName)] = 0;
		name = findData.cFileName;
	}
	if (expName.find(L'~') != wstr_t::npos) {
		ProcessExpanded(path);
		n = path.size();
		if (n > 0 && path[n - 1] != PathDelimiterW)
			path += PathDelimiterW;
	}
	expName = path + name;
	if (was_bs)
		expName += PathDelimiterW;
}

void GetLongPath(wstr_t &pathName)
{
	DWORD n = pathName.size();
	if (!n)
		return;
	if (pathName[n - 1] == L':')
		pathName += PathDelimiterW;
	ExpandPathName(pathName);
	if (pathName.find(L'~') != wstr_t::npos)
		ProcessExpanded(pathName);
}*/

bool GetLongPath(wstr_t &pathName)
{
	wchar_t buf[MaxFileNameLen];
	if (GetLongPathNameW(pathName.c_str(), buf, ARRAY_HIGH(buf))) {
		buf[ARRAY_HIGH(buf)] = 0;
		pathName = buf;
		return true;
	}
	else
		return false;
}

bool GetModuleFile(wstr_t &fileName, HMODULE hModule, unsigned int *err)
{
	if (err)
		*err = 0;
	wchar_t buf[MaxFileNameLen];
	if (GetModuleFileNameW(hModule, buf, ARRAY_HIGH(buf))) {
		buf[ARRAY_HIGH(buf)] = 0;
		fileName = buf;
		return true;
	}
	else {
		if (err)
			*err = GetLastError();
		fileName.erase();
		return false;
	}
}

bool GetCurrentFolder(wstr_t &pathName)
{
	wchar_t buf[MaxFileNameLen + 1];
	DWORD n = GetCurrentDirectoryW(MaxFileNameLen, buf);
	if (n > 0 && n < MaxFileNameLen) {
		if (buf[n - 1] != PathDelimiterW)
			buf[n++] = PathDelimiterW;
		buf[n] = 0;
		pathName = buf;
		return true;
	}
	else {
		pathName.erase();
		return false;
	}
}

bool GetTempFolder(wstr_t &pathName)
{
	wchar_t buf[MaxFileNameLen + 1];
	DWORD n = GetTempPathW(MaxFileNameLen, buf);
	if (n > 0 && n < MaxFileNameLen) {
		if (buf[n - 1] != PathDelimiterW)
			buf[n++] = PathDelimiterW;
		buf[n] = 0;
		pathName = buf;
		return true;
	}
	else {
		pathName.erase();
		return false;
	}
}

bool GetTempFile(wstr_t &fileName, const wchar_t *pathName, const wchar_t *prefix, unsigned int unique)
{
	wstr_t temp;
	if (!pathName)
		if (GetTempFolder(temp))
			pathName = temp.c_str();
		else {
			fileName.erase();
			return false;
		}
	if (!prefix)
		prefix = L"tmp";
	wchar_t buf[MAX_PATH];
	if (GetTempFileNameW(pathName, prefix, unique, buf)) {
		SetStr(fileName, buf, ARRAY_COUNT(buf));
		return true;
	}
	fileName.erase();
	return false;
}

bool CreateTempFolder(wstr_t &folderName, const wchar_t *pathName, unsigned int waitTimeMilliseconds,
	unsigned int *err, const wchar_t *prefix, unsigned int unique)
{
	if (!ForceDirectory(pathName, err))
		return false;
	wstr_t path;
	if (GetTempFile(path, pathName, prefix)) {
		DeleteFileW((folderName = path).c_str());
		// sometimes DeleteFile works in a strange way: the function returns TRUE as if the file has been
		// deleted successfully but attempting to create a folder with the same name shortly after the
		// deletion fails so try several times
		bool success = CreateDirectoryW(folderName.c_str(), 0) != FALSE;
		if (!success && waitTimeMilliseconds > 0) // retry
			while (1) {
				const DWORD sleepTime = 250;
				Sleep(sleepTime);
				success = CreateDirectoryW(folderName.c_str(), 0) != FALSE;
				if (success || waitTimeMilliseconds < sleepTime)
					break;
				waitTimeMilliseconds -= sleepTime;
			}
		if (err)
			*err = GetLastError();
		return success;
	}
	else {
		if (err)
			*err = GetLastError();
		return false;
	}
}

bool GetWindowsFolder(wstr_t &pathName)
{
	wchar_t buf[MaxFileNameLen + 1];
	DWORD n = GetWindowsDirectoryW(buf, MaxFileNameLen);
	if (n > 0 && n < MaxFileNameLen) {
		if (buf[n - 1] != PathDelimiterW)
			buf[n++] = PathDelimiterW;
		buf[n] = 0;
		pathName = buf;
		return true;
	}
	else {
		pathName.erase();
		return false;
	}
}

bool GetSystemFolder(wstr_t &pathName)
{
	wchar_t buf[MaxFileNameLen + 1];
	DWORD n = GetSystemDirectoryW(buf, MaxFileNameLen);
	if (n > 0 && n < MaxFileNameLen) {
		if (buf[n - 1] != PathDelimiterW)
			buf[n++] = PathDelimiterW;
		buf[n] = 0;
		pathName = buf;
		return true;
	}
	else {
		pathName.erase();
		return false;
	}
}

bool GetSystemWindowsFolder(wstr_t &pathName)
{
	wchar_t buf[MaxFileNameLen + 1];
	DWORD n = GetSystemWindowsDirectoryW(buf, MaxFileNameLen);
	if (n > 0 && n < MaxFileNameLen) {
		if (buf[n - 1] != PathDelimiterW)
			buf[n++] = PathDelimiterW;
		buf[n] = 0;
		pathName = buf;
		return true;
	}
	else {
		pathName.erase();
		return false;
	}
}

bool FileExists(const wchar_t *fileName)
{
	DWORD code = GetFileAttributesW(fileName);
	return (code & FILE_ATTRIBUTE_DIRECTORY) == 0;
}

bool FileExists(const char *fileName)
{
	DWORD code = GetFileAttributesA(fileName);
	return (code & FILE_ATTRIBUTE_DIRECTORY) == 0;
}

bool DirectoryExists(const wchar_t *pathName)
{
	DWORD code = GetFileAttributesW(pathName);
	return code != DWORD(-1) && (code & FILE_ATTRIBUTE_DIRECTORY) != 0;
}

bool DirectoryExists(const char *pathName)
{
	DWORD code = GetFileAttributesA(pathName);
	return code != DWORD(-1) && (code & FILE_ATTRIBUTE_DIRECTORY) != 0;
}

bool ForceDirectory(const wstr_t &dirName, unsigned int *err)
{
	if (err)
		*err = 0;
	size_t sz = dirName.size();
	if (sz == 0)
		return false;
	wstr_t path = dirName;
	if (dirName[sz - 1] == PathDelimiterW)
		path.erase(--sz, 1);
	wstr_t base_path;
	ExtractFilePath(path, base_path);
	if (sz < 3 || sz == base_path.size() || DirectoryExists(path.c_str()))
		return true;
	if (ForceDirectory(base_path, err)) {
		BOOL b = ::CreateDirectoryW(path.c_str(), 0);
		if (err)
			*err = GetLastError();
		return b != FALSE;
	}
	else
		return false;
}

bool ReadFromFile(const wchar_t *fileName, void *buf, unsigned int *size, unsigned int *err)
{
	DWORD toRead = *size;
	*size = 0;
	if (err)
		*err = 0;
	bool ret = false;
    HANDLE hFile = CreateFileW(fileName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
	if (hFile != INVALID_HANDLE_VALUE) {
		DWORD wasRead;
		ret = ReadFile(hFile, buf, toRead, &wasRead, 0) != FALSE;
		if (ret)
			*size = wasRead;
		else
			if (err)
				*err = GetLastError();
		CloseHandle(hFile);
	}
	else
		if (err)
			*err = GetLastError();
	return ret;
}

__int64 GetFileSize(const wchar_t *fileName, unsigned int *err)
{
	if (err)
		*err = 0;
	__int64 ret = -1;
    HANDLE hFile = CreateFileW(fileName, 0, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
	if (hFile != INVALID_HANDLE_VALUE) {
		DWORD hiSz = -1;
		DWORD loSz = ::GetFileSize(hFile, &hiSz);
		if (DWORD(loSz) == INVALID_FILE_SIZE) {
			DWORD code = GetLastError();
			if (code != NO_ERROR) {
				if (err)
					*err = code;
				return -1;
			}
		}
		((DWORD*)&ret)[0] = loSz;
		((DWORD*)&ret)[1] = hiSz;
		CloseHandle(hFile);
	}
	else
		if (err)
			*err = GetLastError();
	return ret;
}

__int64 GetFileSize(const char *fileName, unsigned int *err)
{
	size_t len = strlen(fileName);
	assert(len <= INT_MAX);
	wchar_t *wbuf = (wchar_t*)_alloca((len + 1) * 2);
	int n = MultiByteToWideChar(CP_ACP, 0, fileName, (int)len, wbuf, (int)len);
	if (n <= 0)
		return false;
	wbuf[n] = 0;
	return GetFileSize(wbuf, err);
}

#if(_WIN32_WINNT >= 0x0400)
bool GetFileInfo(const wchar_t *fileName, LPBY_HANDLE_FILE_INFORMATION fileInfo, unsigned int *err)
{
	if (err)
		*err = 0;
	bool ret;
    HANDLE hFile = CreateFileW(fileName, 0, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
	if (hFile != INVALID_HANDLE_VALUE) {
		ret = ::GetFileInformationByHandle(hFile, fileInfo) != FALSE;
		if (!ret && err)
			*err = GetLastError();
		CloseHandle(hFile);
	}
	else {
		if (err)
			*err = GetLastError();
		ret = false;
	}
	return ret;
}

bool GetFileInfo(const char *fileName, LPBY_HANDLE_FILE_INFORMATION fileInfo, unsigned int *err)
{
	size_t len = strlen(fileName);
	assert(len <= INT_MAX);
	wchar_t *wbuf = (wchar_t*)_alloca((len + 1) * 2);
	int n = MultiByteToWideChar(CP_ACP, 0, fileName, (int)len, wbuf, (int)len);
	if (n <= 0)
		return false;
	wbuf[n] = 0;
	return GetFileInfo(wbuf, fileInfo, err);
}
#endif

bool ForceDeleteFile(const wchar_t *fileName)
{
	if (DeleteFileW(fileName))
		return true;
	DWORD attr = GetFileAttributesW(fileName);
	if (attr == DWORD(-1)/*INVALID_FILE_ATTRIBUTES*/)
		return false;
	if ((attr & FILE_ATTRIBUTE_READONLY) != 0 && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
		if (!SetFileAttributesW(fileName, attr & ~FILE_ATTRIBUTE_READONLY))
			return false;
		return DeleteFileW(fileName) != FALSE;
	}
	return false;
}

bool ForceDeleteFolder(const wchar_t *folderName)
{
	if (RemoveDirectoryW(folderName))
		return true;
	DWORD attr = GetFileAttributesW(folderName);
	if (attr == DWORD(-1)/*INVALID_FILE_ATTRIBUTES*/)
		return false;
	if ((attr & FILE_ATTRIBUTE_READONLY) != 0 && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0) {
		if (!SetFileAttributesW(folderName, attr & ~FILE_ATTRIBUTE_READONLY))
			return false;
		return RemoveDirectoryW(folderName) != FALSE;
	}
	return false;
}

bool FileClearReadOnlyAttr(const wchar_t *fileName)
{
	DWORD attr = GetFileAttributesW(fileName);
	if (attr == DWORD(-1)/*INVALID_FILE_ATTRIBUTES*/)
		return false;
	if ((attr & FILE_ATTRIBUTE_READONLY) != 0 && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0)
		return SetFileAttributesW(fileName, attr & ~FILE_ATTRIBUTE_READONLY) != FALSE;
	return true;
}

bool SetLastWriteTime(const wchar_t *fileName, const FILETIME* lastWriteTime, unsigned int *err)
{
	if (err)
		*err = 0;
	bool ret;
    HANDLE hFile = CreateFileW(fileName, FILE_WRITE_ATTRIBUTES, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
	if (hFile != INVALID_HANDLE_VALUE) {
		ret = ::SetFileTime(hFile, 0, 0, lastWriteTime) != FALSE;
		if (!ret && err)
			*err = GetLastError();
		CloseHandle(hFile);
	}
	else {
		if (err)
			*err = GetLastError();
		ret = false;
	}
	return ret;
}

bool WideFileName2Ansi(const wchar_t *wideName, str_t &ansiName)
{
	if (AsciiW2Str(wideName, ansiName))
		return true;
	wchar_t shortName[MAX_PATH], *p;
	DWORD n = GetShortPathNameW(wideName, shortName, ARRAY_COUNT(shortName));
	if (n == 0)
		return false;
	if (n <= ARRAY_COUNT(shortName))
		p = shortName;
	else {
		p = (wchar_t*)_alloca((n + 1) * sizeof(*p));
		DWORD m = GetShortPathNameW(wideName, p, n);
		if (m == 0 || m > n)
			return false;
		n = m;
	}
	wstr_t tmp;
	SetStr(tmp, p, n);
	return AsciiW2Str(tmp, ansiName);
}

bool GetDirList(const wchar_t *dirName, wstrvec_t &list, const wchar_t *mask, bool files,
	unsigned int *err)
{
	list.clear();
	return AddDirList(dirName, list, mask, files, err);
}

bool AddDirList(const wchar_t *dirName, wstrvec_t &list, const wchar_t *mask, bool files,
	unsigned int *err)
{
	wstr_t dir = dirName;
	WIN32_FIND_DATAW findFileDataW;
	if (!mask)
		mask = L"*";
	HANDLE hFind = FindFirstFileW((IncludePathDelimiter(dir) + mask).c_str(), &findFileDataW);
	if (hFind != INVALID_HANDLE_VALUE) {
		do {
			bool isDir = (findFileDataW.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
			if (isDir && findFileDataW.cFileName[0] == '.' &&
					(findFileDataW.cFileName[1] == 0 ||
					 findFileDataW.cFileName[1] == '.' && findFileDataW.cFileName[2] == 0)
				) // skip '.' and '..'
				continue;
			if (isDir ^ files)
				list.push_back(findFileDataW.cFileName);
		} while (FindNextFileW(hFind, &findFileDataW));
		DWORD _err = GetLastError();
		FindClose(hFind);
		if (err)
			*err = _err;
		return _err == ERROR_NO_MORE_FILES;
	}
	else {
		if (err)
			*err = GetLastError();
		return false;
	}
}

bool DirectoryContains(const wchar_t *dirName, const wchar_t *fileMask, DirectoryElement acceptWhat)
{
	wstr_t dir = dirName;
	WIN32_FIND_DATAW findFileDataW;
	HANDLE hFind = FindFirstFileW((IncludePathDelimiter(dir) + fileMask).c_str(), &findFileDataW);
	if (hFind != INVALID_HANDLE_VALUE) {
		bool found = false;
		do {
			bool isDir = (findFileDataW.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
			if (isDir && findFileDataW.cFileName[0] == '.' &&
					(findFileDataW.cFileName[1] == 0 ||
					 findFileDataW.cFileName[1] == '.' && findFileDataW.cFileName[2] == 0)
				) // skip '.' and '..'
				continue;
			if (acceptWhat == deAny || (isDir ^ (acceptWhat == deFile))) {
				found = true;
				break;
			}
		} while (FindNextFileW(hFind, &findFileDataW));
		FindClose(hFind);
		return found;
	}
	else
		return false;
}

#if(_WIN32_WINNT >= 0x0400)
bool CopyFolder(const wchar_t *sourceFolder, const wchar_t *targetFolder, const wchar_t *fileMask,
	bool recursive, LPPROGRESS_ROUTINE progress, LPVOID context, unsigned int *err)
{
	if (err)
		*err = 0;
	wstr_t dst = targetFolder;
	if (!StlFunc::ForceDirectory(dst, err))
		return false;
	wstr_t src = sourceFolder;
	WIN32_FIND_DATAW findFileDataW;
	IncludePathDelimiter(dst);
	bool ret = true;
	DWORD _err;
	HANDLE hFind = FindFirstFileW((IncludePathDelimiter(src) + fileMask).c_str(), &findFileDataW);
	if (hFind != INVALID_HANDLE_VALUE) {
		do {
			if ((findFileDataW.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
				if (!CopyFileExW((src + findFileDataW.cFileName).c_str(), (dst + findFileDataW.cFileName).c_str(),
					progress, context, 0, 0))
				{
					_err = GetLastError();
					if (err)
						*err = _err;
					if (_err == ERROR_REQUEST_ABORTED) {
						FindClose(hFind);
						return false;
					}
					ret = false;
				}
		} while (FindNextFileW(hFind, &findFileDataW));
		if (err) {
			_err = GetLastError();
			if (_err != ERROR_NO_MORE_FILES)
				*err = _err;
		}
		FindClose(hFind);
	}
	else {
		if (err)
			*err = GetLastError();
		return false;
	}
	if (recursive) {
		wstrvec_t subDirs;
		if (GetDirList(src.c_str(), subDirs, L"*", false))
			for (wstrvec_cit it = subDirs.begin(); it < subDirs.end(); ++it)
				if (!CopyFolder((src + *it).c_str(), (dst + *it).c_str(), fileMask, true, progress, context, err)) {
					if (_err == ERROR_REQUEST_ABORTED)
						return false;
					ret = false;
				}
	}
	return ret;
}
#endif // (_WIN32_WINNT >= 0x0400)

bool DeleteFolderRecursively(const wchar_t *dirName)
{
	bool ret = true;
	wstr_t dir = dirName;
	WIN32_FIND_DATAW findFileDataW;
	HANDLE hFind = FindFirstFileW((IncludePathDelimiter(dir) + L'*').c_str(), &findFileDataW);
	if (hFind != INVALID_HANDLE_VALUE) {
		do {
			if ((findFileDataW.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
				if (!StlFunc::ForceDeleteFile((dir + findFileDataW.cFileName).c_str()))
					ret = false;
		} while (FindNextFileW(hFind, &findFileDataW));
		FindClose(hFind);
	}
	wstrvec_t subDirs;
	if (GetDirList(dirName, subDirs, L"*", false)) {
		for (wstrvec_cit it = subDirs.begin(); it < subDirs.end(); ++it)
			if (!DeleteFolderRecursively((dir + *it).c_str()))
				ret = false;
	}
	else
		ret = false;
	return ForceDeleteFolder(dirName) != FALSE && ret;
}

__int64 GetFolderSize(const wchar_t *dirName, const wchar_t *fileMask, bool recursive,
	unsigned int *err)
{
	__int64 sz, totalSz = 0;
	unsigned int _err;
	if (err)
		*err = 0;
	wstr_t dir = dirName;
	WIN32_FIND_DATAW findFileDataW;
	HANDLE hFind = FindFirstFileW((IncludePathDelimiter(dir) + fileMask).c_str(), &findFileDataW);
	if (hFind != INVALID_HANDLE_VALUE) {
		do {
			if ((findFileDataW.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
				((DWORD*)&sz)[0] = findFileDataW.nFileSizeLow;
				((DWORD*)&sz)[1] = findFileDataW.nFileSizeHigh;
				totalSz += sz;
			}
		} while (FindNextFileW(hFind, &findFileDataW));
		if (err) {
			_err = GetLastError();
			if (_err != ERROR_NO_MORE_FILES)
				*err = _err;
		}
		FindClose(hFind);
	}
	else
		if (err)
			*err = GetLastError();
	if (recursive) {
		wstrvec_t subDirs;
		if (GetDirList(dirName, subDirs, L"*", false, err))
			for (wstrvec_cit it = subDirs.begin(); it < subDirs.end(); ++it) {
				totalSz += GetFolderSize((dir + *it).c_str(), fileMask, true, &_err);
				if (err && _err)
					*err = _err;
			}
	}
	return totalSz;
}

// *** StdIoFile ***

bool StdIoFile::Close()
{
	if (m_f) {
		int err = fclose(m_f);
		m_f = 0;
		return err == 0;
	}
	return true;
}

// *** TextFileReader ***

bool TextFileReader::Open(const wchar_t *fileName, TextMode textMode)
{
	Close();
	m_f = _wfsopen(fileName, L"rb", _SH_DENYWR);
	if (!m_f)
		return false;
	if (textMode == tmAnsi) {
		m_unicode = false;
		return true;
	}
	if (textMode == tmUtf16) {
		m_unicode = true;
		return true;
	}
	// textMode == tmAuto
	char buf[2];
	size_t n = fread(buf, 1, 2, m_f);
	if (n != 2)
		return false;
	if (buf[0] == (char)0xff && buf[1] == (char)0xfe)
		m_unicode = true;
	else {
		if (fseek(m_f, 0, SEEK_SET) != 0)
			return false;
		m_unicode = buf[1] == 0;
	}
	return true;
}

bool TextFileReader::GetStr(wstr_t &w)
{
	w.erase();
	if (m_unicode) {
		if (fgetws((wchar_t*)buf, sizeof(buf) / sizeof(wchar_t), m_f) == 0)
			return ferror(m_f) == 0;
		w = (wchar_t*)buf;
		return true;
	}
	if (fgets(buf, sizeof(buf), m_f) == 0)
		return ferror(m_f) == 0;
	StlFunc::StrA2W(buf, w);
	return true;
}

// *** TextFileWriter ***

bool TextFileWriter::Create(const wchar_t *fileName, bool unicode)
{
	Close();
	m_f = _wfsopen(fileName, L"wb", _SH_DENYRW);
	if (!m_f)
		return false;
	if (unicode)
		if (fwrite("\377\376", 1, 2, m_f) != 2) // write 0xff, 0xfe bytes
			return false;
	m_unicode = unicode;
	return true;
}

bool TextFileWriter::PutStr(const wchar_t *w)
{
	if (m_unicode)
		return fputws(w, m_f) >= 0;
	str_t s;
	if (!StlFunc::StrW2A(w, s, "?", 0))
		return false;
	return fputs(s.c_str(), m_f) >= 0;
}

bool TextFileWriter::PutLine(const wchar_t *w)
{
	return PutStr(w) && PutStr(L"\r\n");
}

bool TextFileWriter::PutStr(const char *s)
{
	if (!m_unicode)
		return fputs(s, m_f) >= 0;
	wstr_t w;
	if (!StlFunc::StrA2W(s, w))
		return false;
	return fputws(w.c_str(), m_f) >= 0;
}

bool TextFileWriter::PutLine(const char *s)
{
	return PutStr(s) && PutStr("\r\n");
}

// *** FileListEraser ***

void FileListEraser::Set(const wstrvec_t &files)
{
	m_files.clear();
	m_files.resize(files.size());
	std::copy(files.begin(), files.end(), m_files.begin());
}

bool FileListEraser::Erase()
{
	bool res = true;
	for (wstrvec_cit it = m_files.begin(); it < m_files.end(); ++it)
		if (!StlFunc::ForceDeleteFile(it->c_str()))
			res = false;
	m_files.clear();
	return res;
}

#endif // ONLY_NATIVE

}
